package cn.dengta.webapp.trade.service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import javax.annotation.*;

import cn.dengta.common.context.RequestContext;
import cn.dengta.common.model.*;
import cn.dengta.common.util.SpringUtil;
import cn.dengta.common.validator.FormHelper;
import cn.dengta.common.web.Page;
import cn.dengta.context.model.Module;
import cn.dengta.context.model.*;
import cn.dengta.context.web.ProxyRequest;
import cn.dengta.webapp.base.entity.BaseEntity;
import cn.dengta.webapp.trade.config.IntegralTradeProperties;
import cn.dengta.webapp.trade.dao.UserIntegralDao;
import cn.dengta.webapp.trade.dto.*;
import cn.dengta.webapp.trade.entity.*;
import cn.dengta.webapp.trade.entity.meta.InviteUserRule;
import cn.dengta.webapp.trade.model.*;
import cn.dengta.webapp.user.cache.UserCaches;
import cn.dengta.webapp.user.cache.UserSummary;
import cn.dengta.webapp.user.entity.User;
import cn.dengta.webapp.user.model.Gender;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import me.codeplayer.util.*;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Service;

import static cn.dengta.webapp.base.entity.BaseEntity.ID;
import static cn.dengta.webapp.trade.entity.UserIntegral.ACTIVE_AMOUNT;
import static cn.dengta.webapp.trade.entity.UserIntegral.USER_ID;
import static cn.dengta.webapp.trade.model.TradeType.REWARD;

/**
 * 用户积分账户 服务实现类
 */
@Slf4j
@Service
public class UserIntegralService extends AbstractTradeAccountService<UserIntegral, UserIntegralDao> {

	@Resource
	UserIntegralLogService userIntegralLogService;
	@Resource
	UserCaches userCaches;
	@Resource
	AgentService agentService;
	@Resource
	MetaConfigService metaConfigService;
	@Resource
	InviteIntegralLogService inviteIntegralLogService;
	@Resource
	RedisTemplate<String, Object> redisTemplate;
	@Resource
	UserCoinService userCoinService;

	@Resource
	IntegralExchangeCoinConfigService integralExchangeCoinConfigService;
	@Resource
	UserIntegralDailyStatService userIntegralDailyStatService;

	@Resource
	GoddessWeekLogService goddessWeekLogService;

	@Resource
	StringRedisTemplate stringRedisTemplate;

	public void createAccount(Long userId) {
		UserIntegral ui = initAccount(userId);
		save(ui);
	}

	/**
	 * 【主播】获得积分收益（入账）
	 *
	 * @param userId 主播ID
	 * @param log （与该主播进行交互的）用户消费钻石的日志
	 * @param assocObj 对应的业务对象
	 * @param coinTradeType 钻石操作类型
	 */
	public UserIntegralLog addIntegralIncome(Long userId, UserCoinLog log, BaseEntity assocObj, TradeType coinTradeType, @Nullable BigDecimal incomeFinalRatio) {
		final int platformBonus = FormHelper.nullAsZero(log.getPlatformBonus());
		final int platformFreeBonus = FormHelper.nullAsZero(log.getPlatformFreeBonus());
		final int tradeAmount = Math.abs(log.getTradeAmount()) + platformBonus + platformFreeBonus;
		final int freeAmount = Math.abs(log.getTradeFreePart()) + platformFreeBonus;

		UserSummary userSummary = userCaches.get(userId);
		if (userSummary.getWesKit() == null) {
			userSummary = userCaches.get(userId, false);
		}
		BigDecimal agentRate = log.hasAi() ? log.getAiUserIncomeRate() : (coinTradeType == TradeType.COIN_FOR_GIFT ? IntegralTradeProperties.get().calcGiftRate(userSummary, userSummary.getWesKit()) : null);
		BigDecimal profitRate = BigDecimal.ZERO;
		Agent agent = null;
		final boolean liveFlag = Bean.isValidId(log.getLiveId());
		if (userSummary.getAgentId() != null) {
			if (liveFlag || (userSummary.getGender() == Gender.FEMALE)) {
				Agent validAgent = agentService.getValid(userSummary.getAgentId());
				if (validAgent != null) {
					if (!liveFlag || validAgent.hasLiveFlag()) {
						BigDecimal calcAgentRate = validAgent.calcAgentRate(agentRate, liveFlag);// 主播分成
						if (userSummary.canLogin()) {
							profitRate = validAgent.calcUserRate(agentRate, liveFlag); // 推广员抽成
						}
						agentRate = calcAgentRate;
						agent = validAgent;
					}
				}
			}
		}
		BigDecimal inviteRate = calcInviteRate(userSummary, agent, InviteUserRule::getIncomeRate);
		UserIntegralLog integralLog = addIntegralIncome(userId, log.getUserId(), log, assocObj, coinTradeType, TradeType.INTEGRAL_GAIN, tradeAmount, freeAmount, agentRate, Bean.idOf(agent), null, userSummary.getInviteUserId(), incomeFinalRatio);
		if (integralLog != null) {
			if (profitRate.compareTo(BigDecimal.ZERO) > 0 && agent != null) { // 计算主播抽成
				addIntegralIncome(agent.getUserId(), userId, log, new UserIntegralLog().withId(integralLog.getId()), coinTradeType, TradeType.INTEGRAL_GAIN_EXTRACT_USER, tradeAmount, freeAmount, profitRate, agent.getId(), null, null, incomeFinalRatio);
			}
			if (inviteRate != null && inviteRate.compareTo(BigDecimal.ZERO) > 0) {
				addIntegralIncome(userSummary.getInviteUserId(), userId, log, new UserIntegralLog().withId(integralLog.getId()), coinTradeType, TradeType.INTEGRAL_INVITE_GAIN, tradeAmount, freeAmount, agentRate, Bean.idOf(agent), inviteRate, null, incomeFinalRatio);
			}
		}
		return integralLog;
	}

	public BigDecimal calcUserIntegralIncome(@Nonnull UserSummary userSummary, UserCoinLog log, TradeType coinTradeType) {
		final int platformBonus = log.getPlatformBonus();
		final int platformFreeBonus = FormHelper.nullAsZero(log.getPlatformFreeBonus());
		final int tradeAmount = Math.abs(log.getTradeAmount()) + platformBonus + platformFreeBonus;
		final int freeAmount = Math.abs(log.getTradeFreePart()) + platformFreeBonus;
		final IntegralTradeProperties integralTradeProperties = IntegralTradeProperties.get();
		if (userSummary.getWesKit() == null) {
			userSummary = userCaches.get(userSummary.getId(), false);
		}
		BigDecimal agentRate = coinTradeType == TradeType.COIN_FOR_GIFT ? integralTradeProperties.calcGiftRate(userSummary, userSummary.getWesKit()) : null; // 礼物分成单独获取
		final boolean liveFlag = Bean.isValidId(log.getLiveId());
		if (userSummary.getAgentId() != null) {
			if (liveFlag || (userSummary.getGender() == Gender.FEMALE)) {
				Agent validAgent = agentService.getValid(userSummary.getAgentId());
				if (validAgent != null) {
					if (!liveFlag || validAgent.hasLiveFlag()) {
						agentRate = validAgent.calcAgentRate(agentRate, liveFlag);// 主播分成
					}
				}
			}
		}
		return integralTradeProperties.calcFinalIntegralIncomeTotal(tradeAmount, freeAmount, agentRate, userSummary.getWesKit());
	}

	/**
	 * 获取邀请用户配置
	 *
	 * @param userSummary 用户
	 * @param agent 用户推广
	 */
	public BigDecimal calcInviteRate(UserSummary userSummary, Agent agent, Function<InviteUserRule, Integer> mapper) {
		final Long inviteUserId = userSummary.getInviteUserId();
		int inviteRate = 0;
		if (userSummary.hasBindInvite() && (agent == null || !agent.getUserId().equals(inviteUserId))) { // 同时存在邀请与推广奖励，只生效推广奖励
			UserSummary inviteUserSummary = userCaches.get(inviteUserId);
			if (inviteUserSummary != null && inviteUserSummary.isMember() && inviteUserSummary.canLogin()) {
				Language language = Language.en;
				if (userSummary.getWesKit() == null) {
					userSummary = userCaches.get(userSummary.getId(), false);
				}
				if (userSummary.getWesKit().isInterior()) {
					language = Language.zh_CN;
				}
				InviteUserRule cfg = metaConfigService.checkConfig(false, language, MetaConfigType.INVITE_USER_RULE, false);
				if (cfg != null) {
					inviteRate = NumberUtil.getInt(mapper.apply(cfg), 0);
				}
			}
		}
		return BigDecimal.valueOf(inviteRate);
	}

	/**
	 * 【推荐人】获得充值收益（入账）
	 *
	 * @param log 被推荐人充值记录日志
	 * @param assocObj 对应的业务对象
	 */
	public void addInviteRechargeIncome(@Nonnull UserCoinLog log, BaseEntity assocObj) {
		if (TradeType.COIN_RECHARGE.eq(log.getTradeType())) {
			UserSummary toUser = userCaches.get(log.getUserId());
			if (toUser != null) {
				BigDecimal rechargeRate = calcInviteRate(toUser, null, InviteUserRule::getRechargeRate);
				if (rechargeRate.compareTo(BigDecimal.ZERO) > 0) {
					addIntegralIncome(toUser.getInviteUserId(), log.getUserId(), log, assocObj, TradeType.COIN_RECHARGE, TradeType.INTEGRAL_INVITE_GAIN,
							log.getTradeAmount() - log.getTradeFreePart(), 0, null, null, rechargeRate, null, null);
				}
			}
		}
	}

	private UserIntegralLog addIntegralIncome(Long userId, Long toUserId, UserCoinLog log, BaseEntity assocObj, TradeType coinTradeType,
	                                          TradeType tradeType, int tradeAmount, int freeAmount, @Nullable BigDecimal agentRate,
	                                          @Nullable Long agentId, @Nullable BigDecimal awardRate, Long inviteId, @Nullable BigDecimal incomeFinalRatio) {
		final IntegralTradeProperties cfg = IntegralTradeProperties.get();
		UserSummary userSummary = userCaches.get(userId);
		if (userSummary.getWesKit() == null) {
			userSummary = userCaches.get(userId, false);
		}
		Pair<BigDecimal, BigDecimal> income = cfg.calcFinalIntegralIncome(tradeAmount, freeAmount, agentRate, userSummary.getWesKit());
		BigDecimal integralIncome = IntegralTradeProperties.calcFinalIntegralIncomeTotal(income);
		if (incomeFinalRatio != null) { // 计算最终收益
			Assert.isTrue(incomeFinalRatio.compareTo(BigDecimal.ZERO) >= 0 && incomeFinalRatio.compareTo(BigDecimal.valueOf(2)) <= 0);
			integralIncome = integralIncome.multiply(incomeFinalRatio).setScale(2, RoundingMode.HALF_DOWN);
		}
		BigDecimal totalIntegral = null;
		switch (tradeType) {
			case INTEGRAL_INVITE_GAIN:
				if (awardRate == null || integralIncome.compareTo(BigDecimal.ZERO) <= 0 || awardRate.compareTo(BigDecimal.ZERO) <= 0) {
					return null;
				}
				totalIntegral = integralIncome;
				integralIncome = integralIncome.multiply(awardRate).divide(Arith.HUNDRED, 2, RoundingMode.HALF_DOWN);
				break;
			default:
		}
		if (integralIncome.compareTo(BigDecimal.ZERO) > 0) {
			IntegralTradeDTO tradeDTO = IntegralTradeDTO.of(userId, tradeType, toUserId, integralIncome,
					assocObj, log.getAddIp(), log.getAddTime(), coinTradeType).setAgentId(agentId).setInviteUserId(inviteId);
			tradeDTO.setUserCoinLogId(log.getId())
					.setLiveId(log.getLiveId());
			tradeDTO.setAiId(log.getAiId());
			tradeDTO.setRechargeAmount(income.getLeft());
			tradeDTO.setFreeAmount(income.getRight());
			tradeDTO.buildCoinForm(tradeAmount, freeAmount);
			if (TradeType.INTEGRAL_INVITE_GAIN == tradeType) { // 邀请奖励不保存 IntegralLog 记录，单独保存
				inviteIntegralLogService.log(tradeDTO, totalIntegral, awardRate);
				return null;
			}
			UserIntegralLog target = trade(tradeDTO);
			if (target != null && TradeType.INTEGRAL_GAIN == tradeType) {
				addIntimacy(userId, toUserId, integralIncome);
			}
			return target;
		}
		return null;
	}

	/**
	 * 增加魅力值
	 */
	private void addIntimacy(Long userId, Long toUserId, BigDecimal tradeAmount) {
		if (Module.INTIMACY.isEnabled() && Bean.isValidId(toUserId) && tradeAmount.compareTo(BigDecimal.ZERO) > 0) {
			ZSetOperations<String, Object> opsForZSet = redisTemplate.opsForZSet();
			double currentIntimacy = NumberUtil.getDouble(opsForZSet.incrementScore(RedisKey.INTIMACY_INCOME, userId.toString(), tradeAmount.doubleValue()), 0D);
			log.info("【intimacy】用户{} 支出用户={} 增加魅力值={} 总魅力值={}", userId, toUserId, tradeAmount, currentIntimacy);
		}
	}

	/**
	 * 解冻积分收益
	 */
	public void unfreeze() {
		Date endTime = new EasyDate().addDay(-1).endOf(Calendar.DAY_OF_MONTH).toDate();
		// TODO 如果数据量过大，也可能会出现问题
		List<UserIntegralLog> logs = userIntegralLogService.findFrozenIncomeGroup(endTime);
		if (logs.isEmpty()) {
			return;
		}
		userIntegralLogService.makeIncomeActive(endTime);
		Date now = new Date();
		for (UserIntegralLog log : logs) {
			IntegralTradeDTO dto = IntegralTradeDTO.of(log.getUserId(), TradeType.INTEGRAL_UNFREEZE, null,
					log.getTradeAmount(), log, "127.0.0.1", now);
			trade(dto);
		}
	}

	public Page<UserIntegralDTO> findPage(Page<UserIntegral> page, boolean existActive, User input) {
		QueryWrapper<UserIntegral> wrapper = new QueryWrapper<>();
		wrapper.gt(existActive, "log." + ACTIVE_AMOUNT, 0);
		if (input != null) {
			wrapper.eq(input.getId() != null, "log." + USER_ID, input.getId());
			wrapper.like(StringUtil.notEmpty(input.getEmail()), "u.email", input.getEmail());
			wrapper.like(StringUtil.notEmpty(input.getNickname()), "u.nickname", input.getNickname());
			wrapper.eq(input.getAgentId() != null, "u." + User.AGENT_ID, input.getAgentId());
		}
		wrapper.orderByDesc("log." + ID);
		return baseDao.findPage(page, wrapper);
	}

	/**
	 * 处理【积分】交易行为，并进行相应的日志记录
	 *
	 * @return 如果交易成功，将返回 null。
	 */
	@Nullable
	public UserIntegralLog trade(IntegralTradeDTO dto) {
		final TradeType tradeType = dto.getTradeType();
		String remark = StringUtil.toString(dto.getRemark());
		// 避免传参错误
		Assert.isTrue(!tradeType.isForCoinOrIntegral());

		if (REWARD.contains(tradeType)) {
			dto.setFreeAmount(dto.getTradeAmount());
		}

		// TODO 防止并发请求
		BaseEntity assocObj = dto.getAssocObj();

		final Long userId = dto.getFromUserId();
		BigDecimal tradeAmount = dto.getTradeAmount();
		final Long toUserId = dto.getToUserId();

		final String ip = dto.getIp();
		final Date now = dto.getNow();

		UserIntegral account = dto.getUserIntegralForUpdate();
		if (account == null) {
			account = getByUserIdForUpdate(userId);
		} else {
			Assert.isTrue(userId.equals(account.getUserId()));
		}
		Pair<Long, Long> pair = agentService.getAgentPair(dto.getAgentId());
		// 发起方交易日志
		if (tradeType == TradeType.INTEGRAL_SYSTEM_DEDUCT && Status.asResult(dto.getResetStatus())) { // 平台扣除积分重置先把积分清0，在扣除
			tradeAmount = tradeAmount.add(BigDecimal.ZERO.subtract(account.getActiveAmount()).negate());
		}
		UserIntegralLog log = UserIntegralLog.trade(tradeType, true, account, tradeAmount);
		log.setRechargeAmount(NumberUtil.getBigDecimal(dto.getRechargeAmount(), BigDecimal.ZERO));
		log.setFreeAmount(NumberUtil.getBigDecimal(dto.getFreeAmount(), BigDecimal.ZERO));
		log.handleAssocObject(assocObj);
		log.assocCoinForm(dto.getCoinForm());
		log.setAiId(dto.getAiId());
		log.setUserId(userId)
				.setToUserId(toUserId)
				.setAddIp(ip)
				.setAgentId(pair.getLeft())
				.setAgent2Id(pair.getRight())
				.setLiveId(dto.getLiveId())
				.setInviteUserId(dto.getInviteUserId())
				.setRemark(remark)
				.setCoinTradeType(null != dto.getCoinTradeType() ? dto.getCoinTradeType().getValue() : null)
				.setToUserCoinLogId(dto.getUserCoinLogId())
				.setHandleStatus(tradeType.isHandleToConfirm() ? HandleStatus.INIT.getValue() : HandleStatus.DONE.getValue())
				.initTime(now);
		userIntegralLogService.save(log);
		if (tradeType == TradeType.INTEGRAL_CASH_APPLY) {
			account.addFlag(UserIntegral.BIZ_FLAG_CASHED);
		}
		// 异步汇总当日收益
		SpringUtil.asyncRun(() -> userIntegralDailyStatService.stat(userId, dto.getCoinTradeType(), dto.getTradeAmount(), dto.getNow()));
		// 女神活动计算周期积分
		final GoddessWeekType weekType = GoddessWeekType.current(now.getTime());
		final TimeInterval interval = weekType.calcWeek(now.getTime());
		final WesKit wesKit = WesKit.ofIdWesKit(userId);
		UserSummary summary = userCaches.get(userId);
		update(account);
		return log;
	}

	@Override
	protected UserIntegral initAccount(@Nonnull Long userId) {
		return new UserIntegral()
				.init(userId, RequestContext.get().now());
	}

	public Messager<UserIntegralLog> exchangeCoin(ProxyRequest q, User user, IntegralExchangeCoinConfig integralExchangeCoinConfig) {
		UserIntegral userIntegral = this.getByUserIdForUpdate(user.getId());
		if (userIntegral.hasNoEnough(integralExchangeCoinConfig.getIntegral())) {
			return userIntegral.responseForNoBalance();
		}
		BigDecimal curActiveAmount = userIntegral.getActiveAmount();
		CoinTradeDTO coinTradeDTO = CoinTradeDTO.of(user.getId(), TradeType.COIN_BY_INTEGRAL, null, integralExchangeCoinConfig.getCoin(), IntegralExchangeCoinConfig.IntegralExConfig.of(integralExchangeCoinConfig), RequestContext.get());
		Messager<UserCoinTradeDTO> trade = userCoinService.tradeCoin(coinTradeDTO);
		Assert.isTrue(trade != null && trade.isOK(), "钻石兑换失败");
		UserCoinLog coinLog = trade.data().getLog();
		IntegralTradeDTO integralTradeDTO = IntegralTradeDTO.of(user.getId(), TradeType.INTEGRAL_EX_COIN, null, integralExchangeCoinConfig.getIntegral(), coinLog, q.getIP(), q.now(), TradeType.COIN_BY_INTEGRAL);
		integralTradeDTO.setUserIntegralForUpdate(userIntegral);
		integralTradeDTO.setUserCoinLogId(coinLog.getId());
		UserIntegralLog log = trade(integralTradeDTO);
		Assert.isTrue(curActiveAmount.subtract(integralExchangeCoinConfig.getIntegral()).compareTo(userIntegral.getActiveAmount()) == 0, "金额错误，联系客服处理！");
		SpringUtil.asyncRun(() -> integralExchangeCoinConfigService.incCount(integralExchangeCoinConfig.getId()));
		return Messager.hideData(log);
	}

}
